文章/Matplotlib/第 35 章
rcParams 与常用键
Matplotlib · 第 35 / 43 章
样式分三层,优先级从低到高:内置默认 → rcParams / .mplstyle → 函数参数。前两章调的都是第三层(plot(lw=2) 只管这一条线),这一章管中间那层——一处写完,整张图、整个脚本、整个项目都跟着变。
rcParams 不是一个配置文件,它是运行时的一个字典:matplotlib.rcParams['font.size'] = 12 改完之后,后创建的每一个 Text、Line2D 都会按 12 来实例化。意识到“改的是模板、不是已存在的对象”,这一章大部分坑就都不会踩。
三条写入路径:rcParams / rc / rc_context
import matplotlib as mpl
mpl.rcParams['font.size'] = 12 # ① 逐键赋值,全局生效
mpl.rc('lines', linewidth=2, marker='s') # ② 按组简写:lines.* 一起写
with mpl.rc_context({'axes.grid': True}): # ③ 临时生效,出栈自动还原
...
mpl.rcdefaults() # ④ 恢复 matplotlib 内置默认| 写法 | 一句话 | 影响范围 |
|---|---|---|
rcParams['a.b'] = v |
直接改字典 | 立刻生效,此后所有新对象 |
mpl.rc('a', b=v) |
省掉前缀的批量赋值 | 同 rcParams,只是少打字 |
mpl.rc_context({...}) |
上下文管理器 | 只在这个 with 块里生效 |
mpl.rcdefaults() |
全部还原成出厂值 | 全局,不会回到你的 matplotlibrc |
mpl.rc_file_defaults() |
还原成 matplotlibrc 里的值 | 全局 |
mpl.rc_file(path) |
从文件覆盖 | 默认先重置再读,即“整套替换” |
最小示例
import matplotlib as mpl
import matplotlib.pyplot as plt
print(len(mpl.rcParams))
# 输出: 322
print(mpl.rcParams['font.size'], mpl.rcParams['axes.titlesize'])
# 输出: 10.0 large
mpl.rcParams['font.size'] = 16
fig, ax = plt.subplots()
ax.plot([1, 2, 3])
print(ax.title.get_fontsize(), ax.xaxis.label.get_fontsize())
# 输出: 19.2 16.0
plt.show()font.size 是 10 时标题 12pt('large' = 1.2×);改成 16 之后标题跟着变成 19.2——因为 axes.titlesize 存的是相对名,不是数字。这一点下面单独一节讲。
len(rcParams) 是 322,即 3.10.7 一共暴露 322 个可调键。按前缀分:
import matplotlib as mpl
ks = list(mpl.rcParams)
for pre in ['figure.', 'axes.', 'lines.', 'font.', 'savefig.', 'xtick.', 'legend.']:
print(pre, sum(k.startswith(pre) for k in ks))
# 输出: figure. 24
# 输出: axes. 33
# 输出: lines. 17
# 输出: font. 11
# 输出: savefig. 9
# 输出: xtick. 21
# 输出: legend. 20rcParams['a.b'] = value:直接赋值
键名就是「组.名」,两段用点分开。值会被当场校验,写错类型立刻报错、不会拖到画图时才炸:
import matplotlib as mpl
mpl.rcParams['font.size'] = 14
print(mpl.rcParams['font.size'])
# 输出: 14.0 ← 给整数,存进去变成浮点
mpl.rcParams['axes.grid'] = True
print(mpl.rcParams['axes.grid'])
# 输出: True写不存在的键则直接 KeyError——这也是判断某个键在不在当前版本最快的办法:
import matplotlib as mpl
print('legend.linewidth' in mpl.rcParams)
# 输出: False
try:
mpl.rcParams['legend.linewidth']
except KeyError as e:
print('KeyError', e)
# 输出: KeyError 'legend.linewidth'legend.linewidth 在 3.10.7 里根本不存在(网上一堆“3.11 新增 legend.linewidth“的说法,本机实测没有这个键)。要改图例边框粗细只能拿到 legend 对象再 leg.get_frame().set_linewidth()。
mpl.rc():按组简写
rc(group, **kwargs) 把 group. 前缀省掉,一次写多个键:
import matplotlib as mpl
mpl.rc('lines', linewidth=3, marker='s')
print(mpl.rcParams['lines.linewidth'], mpl.rcParams['lines.marker'])
# 输出: 3.0 s
mpl.rc('font', size=14, family='serif')
print(mpl.rcParams['font.size'], mpl.rcParams['font.family'])
# 输出: 14.0 ['serif'] ← family 内部仍是列表组名写错或键名写错都会报 KeyError,报错里带着拼错的全名:
import matplotlib as mpl
try:
mpl.rc('lines', notakey=1)
except KeyError as e:
print('KeyError', e)
# 输出: KeyError 'Unrecognized key "lines.notakey" for group "lines" and name "notakey"'只能用它改已存在的键,不能注册自己的键。
mpl.rc_context():临时生效,出栈还原
想“这一段用样式 A、下一段用样式 B”,不要手动存旧值再改回来——rc_context 就是干这个的:
import matplotlib as mpl
base = mpl.rcParams['font.size']
with mpl.rc_context({'font.size': 20, 'axes.grid': True}):
print(mpl.rcParams['font.size'], mpl.rcParams['axes.grid'])
# 输出: 20.0 True
print(mpl.rcParams['font.size'], mpl.rcParams['axes.grid'], base)
# 输出: 10.0 False 10.0 ← 出栈已还原出栈发生在 with 块结束处,异常退出也会还原。这是它相对“手动赋值 + 手动还原”的唯一价值。
它只吃字典,不吃 style 名字。想叠一套 style 得用 plt.style.context():
import matplotlib as mpl
import matplotlib.pyplot as plt
try:
with mpl.rc_context(['seaborn-v0_8-whitegrid']):
pass
except ValueError as e:
print('ValueError', e)
# 输出: ValueError too many values to unpack (expected 2)
with plt.style.context(['seaborn-v0_8-whitegrid', {'axes.grid': False}]):
print(mpl.rcParams['axes.grid'], mpl.rcParams['grid.color'])
# 输出: False .8报错信息 too many values to unpack (expected 2) 完全看不出问题在哪——它把列表当成了 (key, value) 二元组在拆。记住:rc_context 只收字典,style.context 才收 style。
mpl.rcdefaults() 与 mpl.rc_file_defaults():还原
import matplotlib as mpl
mpl.rcParams['font.size'] = 33
mpl.rcdefaults()
print(mpl.rcParams['font.size'], len(mpl.rcParams))
# 输出: 10.0 322rcdefaults():还原成代码里写死的出厂值。rc_file_defaults():重新读 matplotlibrc 文件(mpl.matplotlib_fname()指的那个)。
本机这两个结果相同,因为 matplotlibrc 是原封不动的模板,没改过任何键:
import matplotlib as mpl
mpl.rcParams['font.size'] = 33
mpl.rc_file_defaults()
print(mpl.rcParams['font.size'])
# 输出: 10.0 ← 本机 matplotlibrc 没动过,所以和 rcdefaults 结果一样
print(mpl.matplotlib_fname().split('site-packages')[-1])
# 输出: \matplotlib\mpl-data\matplotlibrc但如果你把 font.size: 13 写进了 mpl.matplotlib_fname() 那个文件,rc_file_defaults() 会得到 13、rcdefaults() 仍是 10.0。区别只在“有没有改过全局 rc 文件”。
mpl.get_configdir() 是用户级配置目录(C:\Users\wx\.matplotlib),把 matplotlibrc 放这儿也能生效,优先级高于 site-packages 里的模板。
mpl.rc_file() / rc_params_from_file():从文件读
注意 rc_file 有个 use_default_template 参数,默认 True,含义是先按 matplotlibrc 模板重置一遍,再应用文件里的键:
import matplotlib as mpl
from pathlib import Path
Path('X_myrc.txt').write_text(
'font.size: 15\naxes.grid: True\nlines.linewidth: 4\n', encoding='utf-8')
mpl.rcParams['axes.facecolor'] = '#ffeeee'
mpl.rc_file('X_myrc.txt') # 默认 use_default_template=True
print(mpl.rcParams['font.size'], mpl.rcParams['axes.grid'],
mpl.rcParams['axes.facecolor'])
# 输出: 15.0 True white ← facecolor 被重置回 white,我设的 #ffeeee 丢了所以默认行为是“整套替换”:读到的是「模板 + 文件里的 3 个键」,你自己之前改的东西一律不在。
use_default_template=False 才是“只覆盖文件里出现的键”:
import matplotlib as mpl
from pathlib import Path
Path('X_myrc.txt').write_text(
'font.size: 15\naxes.grid: True\nlines.linewidth: 4\n', encoding='utf-8')
mpl.rcParams['axes.facecolor'] = '#ffeeee'
mpl.rc_file('X_myrc.txt', use_default_template=False)
print(mpl.rcParams['font.size'], mpl.rcParams['axes.facecolor'])
# 输出: 15.0 #ffeeee ← 只改了 font.size,facecolor 保留rc_params_from_file() 更纯:不写 rcParams,返回一个 RcParams 让你自己决定怎么用:
import matplotlib as mpl
from pathlib import Path
Path('X_myrc.txt').write_text(
'font.size: 15\naxes.grid: True\nlines.linewidth: 4\n', encoding='utf-8')
d = mpl.rc_params_from_file('X_myrc.txt', use_default_template=False)
print(type(d).__name__, len(d), d['font.size'])
# 输出: RcParams 3 15.0 ← 只解析出文件里那 3 个键注意事项
rcParams是dict的子类(RcParams),键按字母序存,不要依赖插入顺序。- 改
rcParams只影响之后创建的对象。已经建好的fig/ax/Text不会回头改(见下一节的字号例子)。 - 写错键 →
KeyError;写错值 →ValueError(后者信息更友好,会把合法值列出来)。 rc_context只收字典;要叠 style 用plt.style.context([...])。rcdefaults()还原的是出厂值,不是你的 matplotlibrc;要回到文件值用rc_file_defaults()。- 脚本里别在模块顶层改
rcParams——import 这个模块的代码会一起被改。用rc_context或plt.style.context圈住作用域。
综合示例:一套“论文出图”预设
把常用项写成字典,用 rc_context 圈住整张图。三格分别是折线、柱、散点,全部吃同一份预设:
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
PAPER = {
'font.size': 9,
'font.family': 'sans-serif',
'font.sans-serif': ['Microsoft YaHei', 'DejaVu Sans'],
'axes.titlesize': 'medium',
'axes.titleweight': 'bold',
'axes.titlepad': 6,
'axes.grid': True,
'axes.grid.axis': 'y',
'grid.color': '#e3e3e3',
'grid.linewidth': 0.6,
'axes.spines.top': False,
'axes.spines.right': False,
'axes.linewidth': 0.8,
'lines.linewidth': 1.2,
'legend.frameon': False,
'figure.dpi': 110,
'savefig.dpi': 300,
}
x = np.linspace(0, 2 * np.pi, 60)
with mpl.rc_context(PAPER):
fig, axs = plt.subplots(1, 3, figsize=(9, 2.8))
axs[0].plot(x, np.sin(x), label='sin')
axs[0].plot(x, np.cos(x), label='cos')
axs[0].set_title('折线')
axs[0].legend()
axs[1].bar(['甲', '乙', '丙', '丁'], [12, 18, 9, 15])
axs[1].set_title('柱状')
rng = np.random.default_rng(7)
axs[2].scatter(rng.normal(0, 1, 120), rng.normal(0, 1, 120), s=14)
axs[2].set_title('散点')
fig.tight_layout()
plt.show()
print(round(mpl.rcParams['lines.linewidth'], 1))
# 输出: 1.5 ← 出栈后回到默认savefig.dpi: 300 只对 savefig 生效,屏幕显示仍是 figure.dpi: 110——两者本来就分离,见「保存与导出」一章。
常用键与它们的真实默认值
网上流传的默认值不少是过时的。下面这批全部是 3.10.7 实测值:
| 分组 | 键 | 默认 | 一句话 |
|---|---|---|---|
| 画布 | figure.figsize |
[6.4, 4.8] |
英寸,不是像素 |
| 画布 | figure.dpi |
100.0 |
屏幕显示精度 |
| 画布 | figure.facecolor |
'white' |
画布底色 |
| 画布 | figure.autolayout |
False |
旧的自动排版开关 |
| 画布 | figure.constrained_layout.use |
False |
新的自动排版开关 |
| 保存 | savefig.dpi |
'figure' |
跟随 figure.dpi,不是固定 300 |
| 保存 | savefig.bbox |
None |
不裁白边 |
| 保存 | savefig.pad_inches |
0.1 |
bbox='tight' 时的留白 |
| 保存 | savefig.facecolor |
'auto' |
跟随画布底色 |
| 字体 | font.size |
10.0 |
字号的基准,其它是相对它 |
| 字体 | font.family |
['sans-serif'] |
大类 |
| 字体 | axes.unicode_minus |
True |
用 U+2212,中文图要关 |
| 坐标 | axes.titlesize |
'large' |
相对名,不是数字 |
| 坐标 | axes.labelsize |
'medium' |
同上 |
| 坐标 | axes.titleweight |
'normal' |
|
| 坐标 | axes.titlepad |
6.0 |
不是 10 |
| 坐标 | axes.linewidth |
0.8 |
轴线粗细 |
| 坐标 | axes.grid |
False |
|
| 坐标 | axes.spines.top / .right |
True |
默认四边框齐全 |
| 线条 | lines.linewidth |
1.5 |
不是 1.0,也不是 1.8 |
| 线条 | lines.markersize |
6.0 |
|
| 刻度 | xtick.direction |
'out' |
|
| 刻度 | xtick.major.size |
3.5 |
不是 4 |
| 刻度 | xtick.minor.visible |
False |
|
| 图例 | legend.frameon |
True |
默认有框 |
| 图例 | legend.loc |
'best' |
|
| 网格 | grid.color |
'#b0b0b0' |
|
| 数学 | mathtext.fontset |
'dejavusans' |
|
| 数学 | text.usetex |
False |
开它要装 LaTeX |

右图只动了 10 个键:font.size(10→11)、axes.titlesize、axes.titleweight、axes.grid、grid.color、grid.linewidth、axes.spines.top、axes.spines.right、lines.linewidth(1.5→2.2)、legend.frameon——视觉效果已经完全是另一套。
逐个核对默认值:
import matplotlib as mpl
for k in ['figure.figsize', 'figure.dpi', 'savefig.dpi', 'savefig.bbox',
'font.size', 'axes.titlepad', 'axes.linewidth',
'lines.linewidth', 'lines.markersize',
'xtick.major.size', 'xtick.minor.visible',
'legend.frameon', 'grid.color']:
print(f'{k:24s} {mpl.rcParams[k]!r}')
# 输出: figure.figsize [6.4, 4.8]
# 输出: figure.dpi 100.0
# 输出: savefig.dpi 'figure'
# 输出: savefig.bbox None
# 输出: font.size 10.0
# 输出: axes.titlepad 6.0
# 输出: axes.linewidth 0.8
# 输出: lines.linewidth 1.5
# 输出: lines.markersize 6.0
# 输出: xtick.major.size 3.5
# 输出: xtick.minor.visible False
# 输出: legend.frameon True
# 输出: grid.color '#b0b0b0'savefig.dpi 的默认值是字符串 'figure',含义是“跟着 figure.dpi 走”。这决定了两件事:不传 dpi 时导出尺寸完全由 figure.dpi 决定;只要 figure.dpi 变了,导出尺寸跟着变——存图精度和屏显精度在默认状态下是同一个数:
import matplotlib.pyplot as plt
from matplotlib.image import imread
fig, ax = plt.subplots(figsize=(2, 1))
fig.savefig('X_dpi_a.png')
print(imread('X_dpi_a.png').shape)
# 输出: (100, 200, 4) ← 200×100 像素 = 2 英寸 × 100 dpi
fig.savefig('X_dpi_b.png', dpi=200)
print(imread('X_dpi_b.png').shape)
# 输出: (200, 400, 4) ← 给了 dpi,覆盖 'figure'
plt.close(fig)要“屏上看 100dpi、投稿 300dpi”,就是 figure.dpi: 100 + savefig.dpi: 300 两行,不要只改后者。
注意事项
axes.titlepad默认 6.0(旧资料写 10)、lines.linewidth默认 1.5(旧资料写 1.8)、xtick.major.size默认 3.5(旧资料写 4)。savefig.dpi是'figure'而不是 300;savefig.bbox是None而不是'tight'。要裁白边得显式给。legend.linewidth在 3.10.7 不存在。改图例边框用leg.get_frame().set_linewidth()。mathtext.default默认'it';'normal'不是合法值(下一节验证)。axes.spines.top/.right默认是True——“去边框”是常见需求,所以到处都在改它,但它不是默认。
字号是相对的:font.size 与 axes.titlesize='large'
font.size 是基准,其余十几个 *.size 键都是相对名:xx-small 到 xx-large 七档,各乘一个固定倍率。

七档相对名的倍率
import matplotlib as mpl
from matplotlib.font_manager import FontProperties
for name in ['xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large']:
print(f'{name:9s} {FontProperties(size=name).get_size_in_points():.2f} pt')
# 输出: xx-small 5.79 pt
# 输出: x-small 6.94 pt
# 输出: small 8.33 pt
# 输出: medium 10.00 pt
# 输出: large 12.00 pt
# 输出: x-large 14.40 pt
# 输出: xx-large 17.28 pt换算关系是 font.size × {0.579, 0.694, 0.833, 1.0, 1.2, 1.44, 1.728}。记住 large = 1.2×、x-large = 1.44×、xx-large = 1.728× 就够用了——它不是等差,是 1.2 的幂。
存的是名字,不是数字
import matplotlib as mpl
print(mpl.rcParams['axes.titlesize'], mpl.rcParams['axes.labelsize'])
# 输出: large medium
print(mpl.rcParams['xtick.labelsize'], mpl.rcParams['legend.fontsize'])
# 输出: medium medium所以在 rcParams 里写 axes.titlesize: 12 和写 axes.titlesize: large 是两种不同的语义:前者钉死 12pt,后者随 font.size 缩放。做主题时几乎总该用相对名,只有“标题就必须 12pt,别管正文多大”时才用数字。
改 font.size 不影响已经建好的 Axes
这是最容易踩的一个坑。字号在 Text 对象创建的那一刻就从 rcParams 解析成了具体 pt,之后再改 font.size 不会回头改它:
import matplotlib as mpl
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot([1, 2])
print(round(ax.title.get_fontsize(), 1))
# 输出: 12.0 ← font.size=10 时 'large' = 12pt
mpl.rcParams['font.size'] = 20
print(round(ax.title.get_fontsize(), 1))
# 输出: 12.0 ← 老 ax 纹丝不动
fig2, ax2 = plt.subplots()
print(round(ax2.title.get_fontsize(), 1))
# 输出: 24.0 ← 新建的 ax2 才吃到 20推论:改 rcParams 必须放在 plt.subplots() 之前。脚本里常见“先建图、后面想起来改字号、结果没生效”,就是因为顺序反了。
反过来说,ax.set_title(..., fontsize=14) 这种显式参数永远赢 rcParams,而且两者可以混用:rcParams 定大局,个别元素单独覆盖。
mathtext.default 没有 'normal'
旧资料里有“3.11 起 mathtext.default 区分 'normal' 和 'italic',老配置要改成 'normal'“的说法。实测 3.10.7 里 'normal' 直接被拒:
import matplotlib as mpl
print(mpl.rcParams['mathtext.default'])
# 输出: it
try:
mpl.rcParams['mathtext.default'] = 'normal'
except ValueError as e:
print('ValueError', str(e)[:96])
# 输出: ValueError Key mathtext.default: 'normal' is not a valid value for mathtext.default; supported values are [合法值是 rm / cal / bfit / it / tt / sf / bf / default / bb / frak / scr / regular / ...,没有 normal。要让公式里的字母直立,写 'regular' 或 'rm':
import matplotlib as mpl
for v in ['it', 'regular', 'rm']:
mpl.rcParams['mathtext.default'] = v
print(v, '→', mpl.rcParams['mathtext.default'])
# 输出: it → it
# 输出: regular → regular
# 输出: rm → rm注意事项
font.size是基准,其余*.size默认都是相对名;改基准等于整体缩放。- 相对字号是 1.2 的幂,不是等差:
small 8.33/medium 10/large 12的间距并不相等。 - 改
rcParams要在建Figure之前;已建对象只能逐个set_fontsize()。 - 显式参数(
fontsize=14)永远优先于rcParams。 mathtext.default='normal'会抛ValueError,用'regular'或'rm'。- 中文图要配
font.sans-serif一起用,并且把axes.unicode_minus设为False——否则负号会变成方框。